#include <set>
#include <string>
#include <optional>
#include <complex>
#include <iostream>

std::optional<int> asInt(const std::string &s);

class Name
{
private:
    std::string first;
    std::optional<std::string> middle;
    std::string last;

public:
    Name(std::string f,
         std::optional<std::string> m,
         std::string l)
        : first{std::move(f)}, middle{std::move(m)}, last{std::move(l)}
    {
    }
    friend std::ostream &operator<<(std::ostream &strm, const Name &n)
    {
        strm << n.first << ' ';
        if (n.middle)
        {
            strm << n.middle.value() << ' ';
        }
        return strm << n.last;
    }
};

void constructor_test();
void value_get_test();
void value_set_test();

int main(int argc, char const *argv[])
{
    constructor_test();
    value_get_test();
    value_set_test();

    for (auto s : {"42", " 077", "hello", "0x33"})
    {
        // convert s to int and use the result if possible:
        std::optional<int> oi = asInt(s);
        if (oi)
        {
            std::cout << "convert '" << s << "' to int: " << *oi << "\n";
        }
        else
        {
            std::cout << "can't convert '" << s << "' to int\n";
        }
    }

    Name n{"Jim", std::nullopt, "Knopf"};
    std::cout << n << '\n';
    Name m{"Donald", "Ervin", "Knuth"};
    std::cout << m << '\n';

    return 0;
}

void value_get_test()
{
    std::optional o{42};
    if (o) // true
    {
        std::cout << "get value succeed, " << o.value() << '\n';
    }
    else
    {
        std::cout << "get value failed" << '\n';
    }
    if (o.has_value()) // true
    {
        std::cout << "get value succeed, " << o.value() << '\n';
    }

    std::optional o2{std::pair{42, "hello"}};
    auto p = *o2;                                              // initializes p as pair<int,string>
    std::cout << o2->first << ", " << o2->second << std::endl; // prints 42, hello
    std::cout << p.first << ", " << p.second << std::endl;     // prints 42, hello

    std::optional<std::string> o3{"hello"};
    std::cout << *o3 << std::endl; // OK: prints ”hello”
    o3 = std::nullopt;
    std::cout << *o3 << std::endl; // undefined behavior
    if (o3)
        std::cout << *o3 << std::endl; // OK (might output nothing)
    try
    {
        std::cout << o3.value() << std::endl; // OK (throws if no value)
    }
    catch (const std::exception &e)
    {
        std::cerr << e.what() << '\n'; // output "Bad optional access"
    }

    std::cout << o3.value_or("fallback") << std::endl; // outputs fallback if no value)
}
void value_set_test()
{
    std::optional<std::complex<double>> o; // has no value
    std::optional ox{77};                  // optional<int> with value 77
    std::cout << o.value() << std::endl;   //
    o = 42;                                // value becomes complex(42.0, 0.0)
    std::cout << o.value() << std::endl;   //
    o = {9.9, 4.4};                        // value becomes complex(9.9, 4.4)
    std::cout << o.value() << std::endl;   //
    o = ox;                                // OK, because int converts to complex<double>
    std::cout << o.value() << std::endl;   //
    o = std::nullopt;                      // o no longer has a value
    // std::cout << o.value() << std::endl;   //
    o.emplace(5.5, 7.7); // value becomes complex(5.5, 7.7)
    std::cout << o.value() << std::endl;
}
void constructor_test()
{
    // constructor
    std::optional<int> o1;
    std::optional<int> o2(std::nullopt);
    std::optional o3{42}; // deduces optional<int>
    std::optional<std::string> o4{"hello"};
    std::optional o5{"hello"}; // deduces optional<const char*>
    std::optional o6{std::complex{3.0, 4.0}};
    std::optional<std::complex<double>> o7{std::in_place, 3.0, 4.0};
    // initialize set with lambda as sorting criterion:
    auto sc = [](int x, int y)
    {
        return std::abs(x) < std::abs(y);
    };
    std::optional<std::set<int, decltype(sc)>> o8{std::in_place, {4, 8, -7, -2, 0, 5}, sc};
    std::optional<std::string> o9{o5};      // OK
    auto o10 = std::make_optional(3.0);     // optional<double>
    auto o11 = std::make_optional("hello"); // optional<const char*>
    auto o12 = std::make_optional<std::complex<double>>(3.0, 4.0);
}

std::optional<int> asInt(const std::string &s)
{
    std::optional<int> ret; // initially no value
    try
    {
        ret = std::stoi(s);
    }
    catch (...)
    {
    }
    return ret;
}